/*
 * Copyright © 2018  Ebrahim Byagowi
 *
 *  This is part of HarfBuzz, a text shaping library.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */

#ifndef HB_AAT_LAYOUT_JUST_TABLE_HH
#define HB_AAT_LAYOUT_JUST_TABLE_HH

#include "hb-aat-layout-common.hh"
#include "hb-ot-layout.hh"
#include "hb-open-type.hh"

#include "hb-aat-layout-morx-table.hh"

/*
 * just -- Justification
 * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6just.html
 */
#define HB_AAT_TAG_just HB_TAG('j','u','s','t')


namespace AAT {

using namespace OT;


struct ActionSubrecordHeader
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (c->check_struct (this));
  }

  HBUINT16      actionClass;    /* The JustClass value associated with this
                                 * ActionSubrecord. */
  HBUINT16      actionType;     /* The type of postcompensation action. */
  HBUINT16      actionLength;   /* Length of this ActionSubrecord record, which
                                 * must be a multiple of 4. */
  public:
  DEFINE_SIZE_STATIC (6);
};

struct DecompositionAction
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (c->check_struct (this));
  }

  ActionSubrecordHeader
                header;
  F16DOT16      lowerLimit;     /* If the distance factor is less than this value,
                                 * then the ligature is decomposed. */
  F16DOT16      upperLimit;     /* If the distance factor is greater than this value,
                                 * then the ligature is decomposed. */
  HBUINT16      order;          /* Numerical order in which this ligature will
                                 * be decomposed; you may want infrequent ligatures
                                 * to decompose before more frequent ones. The ligatures
                                 * on the line of text will decompose in increasing
                                 * value of this field. */
  Array16Of<HBUINT16>
                decomposedglyphs;
                                /* Number of 16-bit glyph indexes that follow;
                                 * the ligature will be decomposed into these glyphs.
                                 *
                                 * Array of decomposed glyphs. */
  public:
  DEFINE_SIZE_ARRAY (18, decomposedglyphs);
};

struct UnconditionalAddGlyphAction
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (c->check_struct (this));
  }

  protected:
  ActionSubrecordHeader
                header;
  HBGlyphID16   addGlyph;       /* Glyph that should be added if the distance factor
                                 * is growing. */

  public:
  DEFINE_SIZE_STATIC (8);
};

struct ConditionalAddGlyphAction
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (c->check_struct (this));
  }

  protected:
  ActionSubrecordHeader
                header;
  F16DOT16      substThreshold; /* Distance growth factor (in ems) at which
                                 * this glyph is replaced and the growth factor
                                 * recalculated. */
  HBGlyphID16   addGlyph;       /* Glyph to be added as kashida. If this value is
                                 * 0xFFFF, no extra glyph will be added. Note that
                                 * generally when a glyph is added, justification
                                 * will need to be redone. */
  HBGlyphID16   substGlyph;     /* Glyph to be substituted for this glyph if the
                                 * growth factor equals or exceeds the value of
                                 * substThreshold. */
  public:
  DEFINE_SIZE_STATIC (14);
};

struct DuctileGlyphAction
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (c->check_struct (this));
  }

  protected:
  ActionSubrecordHeader
                header;
  HBUINT32      variationAxis;  /* The 4-byte tag identifying the ductile axis.
                                 * This would normally be 0x64756374 ('duct'),
                                 * but you may use any axis the font contains. */
  F16DOT16      minimumLimit;   /* The lowest value for the ductility axis that
                                 * still yields an acceptable appearance. Normally
                                 * this will be 1.0. */
  F16DOT16      noStretchValue; /* This is the default value that corresponds to
                                 * no change in appearance. Normally, this will
                                 * be 1.0. */
  F16DOT16      maximumLimit;   /* The highest value for the ductility axis that
                                 * still yields an acceptable appearance. */
  public:
  DEFINE_SIZE_STATIC (22);
};

struct RepeatedAddGlyphAction
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (c->check_struct (this));
  }

  protected:
  ActionSubrecordHeader
                header;
  HBUINT16      flags;          /* Currently unused; set to 0. */
  HBGlyphID16   glyph;          /* Glyph that should be added if the distance factor
                                 * is growing. */
  public:
  DEFINE_SIZE_STATIC (10);
};

struct ActionSubrecord
{
  unsigned int get_length () const { return u.header.actionLength; }

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    if (unlikely (!c->check_struct (this)))
      return_trace (false);
    hb_barrier ();

    switch (u.header.actionType)
    {
    case 0: hb_barrier ();  return_trace (u.decompositionAction.sanitize (c));
    case 1: hb_barrier ();  return_trace (u.unconditionalAddGlyphAction.sanitize (c));
    case 2: hb_barrier ();  return_trace (u.conditionalAddGlyphAction.sanitize (c));
    // case 3: hb_barrier (); return_trace (u.stretchGlyphAction.sanitize (c));
    case 4: hb_barrier ();  return_trace (u.decompositionAction.sanitize (c));
    case 5: hb_barrier ();  return_trace (u.decompositionAction.sanitize (c));
    default: return_trace (true);
    }
  }

  protected:
  union {
  ActionSubrecordHeader         header;
  DecompositionAction           decompositionAction;
  UnconditionalAddGlyphAction   unconditionalAddGlyphAction;
  ConditionalAddGlyphAction     conditionalAddGlyphAction;
  /* StretchGlyphAction stretchGlyphAction; -- Not supported by CoreText */
  DuctileGlyphAction            ductileGlyphAction;
  RepeatedAddGlyphAction        repeatedAddGlyphAction;
  } u;                          /* Data. The format of this data depends on
                                 * the value of the actionType field. */
  public:
  DEFINE_SIZE_UNION (6, header);
};

struct PostcompensationActionChain
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    if (unlikely (!c->check_struct (this)))
      return_trace (false);
    hb_barrier ();

    unsigned int offset = min_size;
    for (unsigned int i = 0; i < count; i++)
    {
      const ActionSubrecord& subrecord = StructAtOffset<ActionSubrecord> (this, offset);
      if (unlikely (!subrecord.sanitize (c))) return_trace (false);
      offset += subrecord.get_length ();
    }

    return_trace (true);
  }

  protected:
  HBUINT32      count;

  public:
  DEFINE_SIZE_STATIC (4);
};

struct JustWidthDeltaEntry
{
  enum Flags
  {
    Reserved1           =0xE000,/* Reserved. You should set these bits to zero. */
    UnlimiteGap         =0x1000,/* The glyph can take unlimited gap. When this
                                 * glyph participates in the justification process,
                                 * it and any other glyphs on the line having this
                                 * bit set absorb all the remaining gap. */
    Reserved2           =0x0FF0,/* Reserved. You should set these bits to zero. */
    Priority            =0x000F /* The justification priority of the glyph. */
  };

  enum Priority
  {
    Kashida             = 0,    /* Kashida priority. This is the highest priority
                                 * during justification. */
    Whitespace          = 1,    /* Whitespace priority. Any whitespace glyphs (as
                                 * identified in the glyph properties table) will
                                 * get this priority. */
    InterCharacter      = 2,    /* Inter-character priority. Give this to any
                                 * remaining glyphs. */
    NullPriority        = 3     /* Null priority. You should set this priority for
                                 * glyphs that only participate in justification
                                 * after the above priorities. Normally all glyphs
                                 * have one of the previous three values. If you
                                 * don't want a glyph to participate in justification,
                                 * and you don't want to set its factors to zero,
                                 * you may instead assign it to the null priority. */
  };

  protected:
  F16DOT16      beforeGrowLimit;/* The ratio by which the advance width of the
                                 * glyph is permitted to grow on the left or top side. */
  F16DOT16      beforeShrinkLimit;
                                /* The ratio by which the advance width of the
                                 * glyph is permitted to shrink on the left or top side. */
  F16DOT16      afterGrowLimit; /* The ratio by which the advance width of the glyph
                                 * is permitted to shrink on the left or top side. */
  F16DOT16      afterShrinkLimit;
                                /* The ratio by which the advance width of the glyph
                                 * is at most permitted to shrink on the right or
                                 * bottom side. */
  HBUINT16      growFlags;      /* Flags controlling the grow case. */
  HBUINT16      shrinkFlags;    /* Flags controlling the shrink case. */

  public:
  DEFINE_SIZE_STATIC (20);
};

struct WidthDeltaPair
{
  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);
    return_trace (c->check_struct (this));
  }

  protected:
  HBUINT32      justClass;      /* The justification category associated
                                 * with the wdRecord field. Only 7 bits of
                                 * this field are used. (The other bits are
                                 * used as padding to guarantee longword
                                 * alignment of the following record). */
  JustWidthDeltaEntry
                wdRecord;       /* The actual width delta record. */

  public:
  DEFINE_SIZE_STATIC (24);
};

typedef OT::Array32Of<WidthDeltaPair> WidthDeltaCluster;

struct JustificationCategory
{
  typedef void EntryData;

  enum Flags
  {
    SetMark             =0x8000,/* If set, make the current glyph the marked
                                 * glyph. */
    DontAdvance         =0x4000,/* If set, don't advance to the next glyph before
                                 * going to the new state. */
    MarkCategory        =0x3F80,/* The justification category for the marked
                                 * glyph if nonzero. */
    CurrentCategory     =0x007F /* The justification category for the current
                                 * glyph if nonzero. */
  };

  bool sanitize (hb_sanitize_context_t *c, const void *base) const
  {
    TRACE_SANITIZE (this);
    return_trace (likely (c->check_struct (this) &&
                          morphHeader.sanitize (c) &&
                          stHeader.sanitize (c)));
  }

  protected:
  ChainSubtable<ObsoleteTypes>
                morphHeader;    /* Metamorphosis-style subtable header. */
  StateTable<ObsoleteTypes, EntryData>
                stHeader;       /* The justification insertion state table header */
  public:
  DEFINE_SIZE_STATIC (30);
};

struct JustificationHeader
{
  bool sanitize (hb_sanitize_context_t *c, const void *base) const
  {
    TRACE_SANITIZE (this);
    return_trace (likely (c->check_struct (this) &&
                          justClassTable.sanitize (c, base, base) &&
                          wdcTable.sanitize (c, base) &&
                          pcTable.sanitize (c, base) &&
                          lookupTable.sanitize (c, base)));
  }

  protected:
  Offset16To<JustificationCategory>
                justClassTable; /* Offset to the justification category state table. */
  Offset16To<WidthDeltaCluster>
                wdcTable;       /* Offset from start of justification table to start
                                 * of the subtable containing the width delta factors
                                 * for the glyphs in your font.
                                 *
                                 * The width delta clusters table. */
  Offset16To<PostcompensationActionChain>
                pcTable;        /* Offset from start of justification table to start
                                 * of postcompensation subtable (set to zero if none).
                                 *
                                 * The postcompensation subtable, if present in the font. */
  Lookup<Offset16To<WidthDeltaCluster>>
                lookupTable;    /* Lookup table associating glyphs with width delta
                                 * clusters. See the description of Width Delta Clusters
                                 * table for details on how to interpret the lookup values. */

  public:
  DEFINE_SIZE_MIN (8);
};

struct just
{
  static constexpr hb_tag_t tableTag = HB_AAT_TAG_just;

  bool sanitize (hb_sanitize_context_t *c) const
  {
    TRACE_SANITIZE (this);

    return_trace (likely (c->check_struct (this) &&
                          hb_barrier () &&
                          version.major == 1 &&
                          horizData.sanitize (c, this, this) &&
                          vertData.sanitize (c, this, this)));
  }

  protected:
  FixedVersion<>version;        /* Version of the justification table
                                 * (0x00010000u for version 1.0). */
  HBUINT16      format;         /* Format of the justification table (set to 0). */
  Offset16To<JustificationHeader>
                horizData;      /* Byte offset from the start of the justification table
                                 * to the header for tables that contain justification
                                 * information for horizontal text.
                                 * If you are not including this information,
                                 * store 0. */
  Offset16To<JustificationHeader>
                vertData;       /* ditto, vertical */

  public:
  DEFINE_SIZE_STATIC (10);
};

} /* namespace AAT */


#endif /* HB_AAT_LAYOUT_JUST_TABLE_HH */
